use cmsis_rtos2_rs::{
    osWaitForever,
    static_mem::{mq_size, EventFlags, MessageQueue, MutexValue, Task, Thread},
};
use utils::{
    defer_ref, log,
    ufmt::{self, derive::uDebug},
};

use crate::cp_heat::{
    driver::{ctrl, APP_INFO},
    status,
};

use super::HeatErr;

#[derive(Clone, Copy, uDebug)]
pub enum State {
    STOP,
    TempFail,
    Timeout,
    Release,
    PreHeat,
    Inject,
}

impl Into<u8> for State {
    fn into(self) -> u8 {
        match self {
            Self::STOP => 0,
            Self::TempFail => 1,
            Self::Timeout => 2,
            Self::Release => 3,
            Self::PreHeat => 4,
            Self::Inject => 5,
        }
    }
}

#[derive(uDebug)]
enum Error {
    StateUpdate(State),
    Other(HeatErr),
    Timeout,
}

const MSG_COUNT: usize = 2;
struct SteamHeatTask {
    mq: MessageQueue<State, MSG_COUNT, { mq_size::<State>(MSG_COUNT) }>,
    state: MutexValue<State>,
    flags: EventFlags,
}
static TASK: SteamHeatTask = SteamHeatTask {
    mq: MessageQueue::uninit(),
    state: MutexValue::uninit(State::STOP),
    flags: EventFlags::uninit(),
};
const FLAG_STOP: u32 = 0x01;
static THREAD: Thread<1024> = Thread::uninit();

impl Task for SteamHeatTask {
    fn run(&self) {
        let mut state = *self.state.lock();
        loop {
            log!("steam:{:?}", state);
            let ret = match state {
                State::Release => self.do_release(),
                State::STOP => self.do_stop(),
                State::TempFail => self.do_temp_fail(),
                State::Timeout => self.do_release(),
                State::PreHeat => self.do_pre_heat(),
                State::Inject => self.do_inject(),
            };
            state = match ret {
                Ok(_) => state,
                Err(Error::Other(HeatErr::Temp)) => State::TempFail,
                Err(Error::Timeout) => State::Timeout,
                Err(Error::StateUpdate(v)) => v,
                Err(Error::Other(_)) => state,
            };
            *self.state.lock() = state;
        }
    }
}

const MAX_DURATION: u32 = 4 * 60 * 1000;

impl SteamHeatTask {
    fn wait(&self, timeout: u32) -> Result<(), Error> {
        if let Ok(v) = self.mq.poll(timeout) {
            Err(Error::StateUpdate(v))
        } else {
            Ok(())
        }
    }

    fn do_inject(&self) -> Result<(), Error> {
        ctrl::steam_pump_open();
        ctrl::v1_open();
        defer_ref!({
            ctrl::steam_pump_close();
            ctrl::v1_close();
            ctrl::heat_steam_close();
        });
        self.heat_loop()
    }

    fn do_pre_heat(&self) -> Result<(), Error> {
        defer_ref!({
            ctrl::heat_steam_close();
        });
        self.heat_loop()
    }

    fn heat_loop(&self) -> Result<(), Error> {
        let heat_temp = APP_INFO.lock().steam_temp;
        let heat_timeout = APP_INFO.lock().steam_heat_timeout as u32 * 1000;
        let mut heat_time_count = 0;
        let mut time_count = 0;
        const DELAY: u32 = 2000;
        loop {
            let temp = status::temp().map_err(Error::Other)?;
            if temp.steam < heat_temp {
                if (heat_time_count % DELAY) == 0 {
                    let diff = heat_temp - temp.steam;
                    match diff {
                        1..20 => {
                            ctrl::heat_steam_set_value(diff as u8);
                        }
                        _ => {
                            ctrl::heat_steam_open();
                        }
                    }
                }
                heat_time_count += 50;
                if heat_time_count >= heat_timeout {
                    return Err(Error::Timeout);
                }
            } else {
                ctrl::heat_steam_close();
                heat_time_count = 0;
            }
            if time_count > MAX_DURATION {
                return Err(Error::StateUpdate(State::Release));
            }
            time_count += 50;
            self.wait(50)?;
        }
    }

    fn do_release(&self) -> Result<(), Error> {
        self.wait(180 * 1000)?;
        Err(Error::StateUpdate(State::STOP))
    }

    fn do_temp_fail(&self) -> Result<(), Error> {
        loop {
            if status::temp().is_ok() {
                break;
            }
            self.wait(500)?;
        }
        Err(Error::StateUpdate(State::STOP))
    }

    fn do_stop(&self) -> Result<(), Error> {
        self.flags.set_flags(FLAG_STOP);
        self.wait(osWaitForever)
    }
}

pub unsafe fn start() {
    TASK.mq.init_once("steam");
    TASK.state.init_once("state");
    TASK.flags.init_once("stop flag");
    THREAD.spawn(&TASK);
}

pub fn ctrl(state: State) {
    _ = TASK.mq.post(&state);
}

// pub fn stop() {
//     TASK.flags.clear_flags(FLAG_STOP);
//     ctrl(State::STOP);
//     _ = TASK.flags.wait_any_flags(FLAG_STOP, osWaitForever);
// }

pub fn state() -> State {
    *TASK.state.lock()
}
